using System;
using System.Runtime.Serialization;
namespace Daemon.Data.Substructure.Framework
{
    public abstract class BaseOperationManager
    {
        public Operation<T> Register<T>(T status, Action<T> removeCallback = null)
            where T : class
        {
            var id = Guid.NewGuid().ToString("N");
            return Register(id, status, removeCallback);
        }

        public Operation<T> Register<T>(string id, T status, Action<T> removeCallback = null)
            where T : class
        {
            var operation = new Operation<T>(id, status);
            DataAdd(id, operation, removeCallback);
            return operation;
        }

        public void Set<T>(string id, T status, double expirationHours = 12)
            where T : class
        {
            var operation = new Operation<T>(id, status);
            UpdateExpiration(id, operation, expirationHours);
        }

        public object Get(string id, double? expirationHours = 12)
        {
            var operation = DataGet<object>(id);
            if (expirationHours.HasValue)
            {
                return UpdateExpiration(id, operation, expirationHours.Value);
            }

            return operation;
        }

        public Operation<T> Get<T>(string id, double? expirationHours = 12)
            where T : class
        {
            var operation = DataGet<Operation<T>>(id);
            if (expirationHours.HasValue)
            {
                return UpdateExpiration(id, operation, expirationHours.Value) as Operation<T>;
            }

            return operation;
        }

        public void Delete(string id)
        {
            DataRemove(id);
        }

        protected abstract T DataGet<T>(string id)
            where T : class;

        protected abstract void DataSet(string id, object operation, DateTime expirationHours);

        protected abstract void DataAdd<T>(string id, object operation, Action<T> removeCallback)
            where T : class;

        protected abstract void DataRemove(string id);

        protected DateTime GetAbsoluteExpiration()
        {
            return DateTime.UtcNow.AddHours(12);
        }

        private object UpdateExpiration(string id, object operation, double expirationHours = 12)
        {
            if (operation != null)
            {
                var updatable = operation as IUpdatable;
                if (updatable != null)
                {
                    updatable.IsUpdating = true;
                }

                DataSet(id, operation, GetAbsoluteExpiration(expirationHours));
                if (updatable != null)
                {
                    updatable.IsUpdating = false;
                }
            }

            return operation;
        }

        private static DateTime GetAbsoluteExpiration(double hours)
        {
            return DateTime.UtcNow.AddHours(hours);
        }
    }

    public interface IUpdatable
    {
        bool IsUpdating { get; set; }
    }

    [DataContract]
    public class Operation<T>
        : IUpdatable
        where T : class
    {
        public Operation(string id, T status)
        {
            Id = id;
            Status = status;
        }

        [DataMember]
        public string Id { get; private set; }

        [DataMember]
        public T Status { get; private set; }

        [DataMember]
        public bool IsUpdating { get; set; }
    }
}